﻿/**
 * grpc客户端测试用例
 * @author ZhroRui
 */

#include <grpcpp/grpcpp.h>

#include <iostream>
#include <memory>
#include <string>

#include "hello.grpc.pb.h"

using grpc::Channel;
using grpc::ClientContext;
using grpc::Status;
using myGrpc::EchoRequest;
using myGrpc::Hello;

/**
 * 作为grpc客户端,使用hello.proto定义的接口,与服务端通信
 */
class HelloClient {
 public:
  /**
   * 作为grpc客户端,测试hello.proto协议,测试与服务端的应答模式和流模式
   * @param channel 客户端到服务端的连接通道
   */
  explicit HelloClient(std::shared_ptr<Channel> channel)
      : stub_(Hello::NewStub(channel)) {
  }

  /**
   * 一一应答测试
   * 客户端发起请求,返回服务端响应的数据
   * @param message 发送给服务端的消息
   * @return 服务端响应的信息
   */
  std::string testEcho(const std::string& message) const {
    // 准备请求的消息
    EchoRequest request{};
    request.set_message(message);

    // 用于接收返回的消息
    EchoRequest reply{};

    // 发起应答查询
    ClientContext context;
    const Status status = stub_->Echo(&context, request, &reply);

    if (status.ok()) {
      // 返回查询到的信息
      return reply.message();
    } else {
      std::cout << "Echo RPC failed: " << status.error_code() << ": "
                << status.error_message() << std::endl;
      return "RPC failed";
    }
  }

  /**
   * 测试客户端发起信息,服务端流式响应
   * @param message 发送给服务端的信息
   */
  void testServerStream(const std::string& message) const {
    // 准备请求的消息
    EchoRequest request{};
    request.set_message(message);

    // 给服务端发送消息,持有服务端的数据流读取器
    ClientContext context{};
    const std::shared_ptr<grpc::ClientReader<myGrpc::EchoRequest>> reader(
        stub_->ServerStream(&context, request));

    // 读取服务端返回的数据流
    EchoRequest reply{};
    while (reader->Read(&reply)) {
      std::cout << "Server Stream Response: " << reply.message() << std::endl;
    }
    // 服务端已经停止流
    const Status status = reader->Finish();
    if (!status.ok()) {
      std::cout << "Server Stream RPC failed: " << status.error_code() << ": "
                << status.error_message() << std::endl;
    }
  }

  /**
   * 测试客户端发送流给服务端,服务端接收所有消息后返回1个消息
   * @param messages 以流的方式给服务端的数据
   * @return 服务端响应的消息
   */
  std::string testClientStream(const std::vector<std::string>& messages) const {
    // 初始化流式写入器
    ClientContext context{};
    EchoRequest reply{};
    std::shared_ptr<grpc::ClientWriter<EchoRequest>> writer(
        stub_->ClientStream(&context, &reply));

    // 进行流写入
    for (const auto& message : messages) {
      // 每一次写入的消息
      EchoRequest request{};
      request.set_message(message);
      // 写入
      writer->Write(request);
    }
    // 写入完毕
    writer->WritesDone();

    // 查看服务端返回状态
    const Status status = writer->Finish();

    if (status.ok()) {
      // 返回服务端响应的消息
      return reply.message();
    } else {
      std::cout << "Client Stream RPC failed: " << status.error_code() << ": "
                << status.error_message() << std::endl;
      return "RPC failed";
    }
  }

  /**
   * 测试双向流
   * 流都是由客户端进行发起
   */
  void testDuplexStream() const {
    // 初始化读写流
    ClientContext context{};
    // auto stream(stub_->DuplexStream(&context));
    std::shared_ptr<grpc::ClientReaderWriter<EchoRequest, EchoRequest>> stream(
        stub_->DuplexStream(&context));

    /* 写入流,向服务端流式写入数据 */
    std::thread writer([stream]() mutable {
      for (int i = 0; i < 5; ++i) {
        // 准备写入的数据
        EchoRequest request{};
        request.set_message("Message " + std::to_string(i));
        // 写入
        stream->Write(request);
      }
      // 写入完毕,结束流
      stream->WritesDone();
    });

    /* 读取流,读取服务端发来的流数据 */
    // 接收服务端流数据的实例
    EchoRequest reply{};
    // 持续读取,直到服务端停止流传输
    while (stream->Read(&reply)) {
      // 处理服务端发来的信息
      std::cout << "Duplex Stream Response: " << reply.message() << std::endl;
    }

    // 等待写入完毕
    writer.join();

    // 查看服务端最后状态
    const Status status = stream->Finish();
    if (!status.ok()) {
      std::cout << "Duplex Stream RPC failed: " << status.error_code() << ": "
                << status.error_message() << std::endl;
    }
  }

 private:
  /** @brief 客户端的存根,调用hello.proto的接口和服务端通信 */
  std::shared_ptr<Hello::Stub> stub_;
};

int main(int argc, char** argv) {
  const HelloClient client(grpc::CreateChannel(
      "localhost:8080", grpc::InsecureChannelCredentials()));

  /* Test Echo */
  const std::string response = client.testEcho("Hello, World!");
  std::cout << "Echo Response: " << response << std::endl;

  /* Test Server Stream */
  std::cout << "Testing Server Stream:" << std::endl;
  client.testServerStream("Hello from Server Stream!");

  /* Test Client Stream */
  std::cout << "Testing Client Stream:" << std::endl;
  const std::vector<std::string> messages{"Hello 1", "Hello 2", "Hello 3"};
  const std::string combined_response = client.testClientStream(messages);
  std::cout << "Client Stream Response: " << combined_response << std::endl;

  /* Test Duplex Stream */
  std::cout << "Testing Duplex Stream:" << std::endl;
  client.testDuplexStream();

  return 0;
}
